package org.mybatis.plugin;

import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.XmlConstants;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.util.StringUtility;
import org.mybatis.generator.plugins.SerializablePlugin;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * @ClassName: MySQLLimitPlugin
 * @author: BANG
 * @Date: 2019/11/20 11:39
 * @Version: 1.0
 * @Modified By:
 * @Desc: TODO
 */
public class MySQLLimitPlugin extends SerializablePlugin {


    private FullyQualifiedJavaType serializable;

    public MySQLLimitPlugin() {
        //$NON-NLS-1$
        serializable = new FullyQualifiedJavaType("java.io.Serializable");
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    /**
     * 为每个Example类添加limit和offset属性已经set、get方法
     */
    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

        PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper();

        Field limit = new Field();
        limit.setName("limit");
        limit.setVisibility(JavaVisibility.PRIVATE);
        limit.setType(integerWrapper);
        topLevelClass.addField(limit);

        Method setLimit = new Method();
        setLimit.setVisibility(JavaVisibility.PUBLIC);
        setLimit.setName("setLimit");
        setLimit.addParameter(new Parameter(integerWrapper, "limit"));
        setLimit.addBodyLine("this.limit = limit;");
        topLevelClass.addMethod(setLimit);

        Method getLimit = new Method();
        getLimit.setVisibility(JavaVisibility.PUBLIC);
        getLimit.setReturnType(integerWrapper);
        getLimit.setName("getLimit");
        getLimit.addBodyLine("return limit;");
        topLevelClass.addMethod(getLimit);

        Field offset = new Field();
        offset.setName("offset");
        offset.setVisibility(JavaVisibility.PRIVATE);
        offset.setType(integerWrapper);
        topLevelClass.addField(offset);

        Method setOffset = new Method();
        setOffset.setVisibility(JavaVisibility.PUBLIC);
        setOffset.setName("setOffset");
        setOffset.addParameter(new Parameter(integerWrapper, "offset"));
        setOffset.addBodyLine("this.offset = offset;");
        topLevelClass.addMethod(setOffset);

        Method getOffset = new Method();
        getOffset.setVisibility(JavaVisibility.PUBLIC);
        getOffset.setReturnType(integerWrapper);
        getOffset.setName("getOffset");
        getOffset.addBodyLine("return offset;");
        topLevelClass.addMethod(getOffset);

        return true;
    }

    @Override
    public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateSelectLimitElement());
        return true;
    }

    @Override
    public boolean sqlMapSelectByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateSelectLimitElement());
        return true;
    }

    @Override
    public boolean sqlMapUpdateByExampleSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateUpdateAndDeleteLimitElement());
        return true;
    }

    @Override
    public boolean sqlMapUpdateByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateUpdateAndDeleteLimitElement());
        return true;
    }

    @Override
    public boolean sqlMapUpdateByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateUpdateAndDeleteLimitElement());
        return true;
    }

    @Override
    public boolean sqlMapDeleteByExampleElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        element.addElement(generateUpdateAndDeleteLimitElement());
        return true;
    }

    private XmlElement generateSelectLimitElement() {
        XmlElement ifLimitNotNullElement = new XmlElement("if");
        ifLimitNotNullElement.addAttribute(new Attribute("test", "limit != null"));

        XmlElement ifOffsetNotNullElement = new XmlElement("if");
        ifOffsetNotNullElement.addAttribute(new Attribute("test", "offset != null"));
        ifOffsetNotNullElement.addElement(new TextElement("limit ${offset}, ${limit}"));
        ifLimitNotNullElement.addElement(ifOffsetNotNullElement);

        XmlElement ifOffsetNullElement = new XmlElement("if");
        ifOffsetNullElement.addAttribute(new Attribute("test", "offset == null"));
        ifOffsetNullElement.addElement(new TextElement("limit ${limit}"));
        ifLimitNotNullElement.addElement(ifOffsetNullElement);

        return ifLimitNotNullElement;
    }

    private XmlElement generateUpdateAndDeleteLimitElement() {
        XmlElement ifLimitNotNullElement = new XmlElement("if");
        ifLimitNotNullElement.addAttribute(new Attribute("test", "limit != null"));
        ifLimitNotNullElement.addElement(new TextElement("limit ${limit}"));
        return ifLimitNotNullElement;
    }

    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
        java.lang.reflect.Field field;
        try {
            field = GeneratedXmlFile.class.getDeclaredField("isMergeable");
            if (!field.isAccessible()) {
                field.setAccessible(Boolean.TRUE);
            }
            field.set(sqlMap, false);
        } catch (NoSuchFieldException e) {

        } catch (SecurityException e) {

        } catch (IllegalArgumentException e) {

        } catch (IllegalAccessException e) {
        }

        return super.sqlMapGenerated(sqlMap, introspectedTable);
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        XmlElement parentElement = document.getRootElement();

        // 此处生成加上Ext
        List<Attribute> attributes = parentElement.getAttributes();
        for (int i = 0; attributes != null && i < attributes.size(); i++) {
            if (attributes.get(i).getName().equals("namespace")) {
                String value = attributes.get(i).getValue();
                attributes.remove(i);
                attributes.add(new Attribute("namespace", value + "Ext"));
                break;
            }
        }
        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }

    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        FullyQualifiedJavaType type = new FullyQualifiedJavaType(introspectedTable.getMyBatis3JavaMapperType() + "Ext");
        Interface interfaze = new Interface(type);
        interfaze.setVisibility(JavaVisibility.PUBLIC);
        context.getCommentGenerator().addJavaFileComment(interfaze);

        String rootInterface = introspectedTable.getMyBatis3JavaMapperType();

        if (StringUtility.stringHasValue(rootInterface)) {
            FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface);
            interfaze.addSuperInterface(fqjt);
            interfaze.addImportedType(fqjt);
        }

        GeneratedJavaFile gjf = new MyExtGeneratedJavaFile(interfaze,
                context.getJavaModelGeneratorConfiguration().getTargetProject(),
                context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING), context.getJavaFormatter());
        List<GeneratedJavaFile> gjfs = super.contextGenerateAdditionalJavaFiles(introspectedTable);
        if (gjfs == null) {
            gjfs = new ArrayList<GeneratedJavaFile>();
        }
        gjfs.add(gjf);
        return gjfs;
    }

    private class MyExtGeneratedJavaFile extends GeneratedJavaFile {
        public MyExtGeneratedJavaFile(CompilationUnit compilationUnit, String targetProject, String fileEncoding,
                                      JavaFormatter javaFormatter) {
            super(compilationUnit, targetProject, fileEncoding, javaFormatter);
        }

        @Override
        public String getFormattedContent() {
            File targetFile = getTargetFile(getTargetProject(), getTargetPackage(), getFileName());
            if (targetFile != null) {
                try {
                    return readFileAsString(targetFile);
                } catch (IOException e) {
                }
            }

            return super.getFormattedContent();
        }

        private String readFileAsString(File filePath) throws IOException {
            StringBuffer fileData = new StringBuffer();
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
            String tempLine = null;
            while ((tempLine = reader.readLine()) != null) {
                fileData.append(tempLine);
                fileData.append("\r\n");
            }
            reader.close();
            return fileData.toString();
        }

        private File getTargetFile(String targetProject, String targetPackage, String targetFileName) {
            File project = new File(targetProject);
            if (!project.isDirectory()) {
                return null;
            }

            StringBuilder sb = new StringBuilder();
            StringTokenizer st = new StringTokenizer(targetPackage, "."); //$NON-NLS-1$
            while (st.hasMoreTokens()) {
                sb.append(st.nextToken());
                sb.append(File.separatorChar);
            }

            File directory = new File(project, sb.toString());
            if (!directory.isDirectory()) {
                return null;
            }

            File targetFile = new File(directory, targetFileName);
            if (targetFile.exists()) {
                return targetFile;
            }

            return null;
        }
    }

    @Override
    public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles(IntrospectedTable introspectedTable) {
        XmlElement answer = new XmlElement("mapper"); //$NON-NLS-1$
        String namespace = introspectedTable.getMyBatis3SqlMapNamespace() + "Ext";
        answer.addAttribute(new Attribute("namespace", //$NON-NLS-1$
                namespace));

        context.getCommentGenerator().addRootComment(answer);

        Document document = new Document(XmlConstants.MYBATIS3_MAPPER_PUBLIC_ID,
                XmlConstants.MYBATIS3_MAPPER_SYSTEM_ID);
        document.setRootElement(answer);

        String xmlMapperFileExtName = introspectedTable.getFullyQualifiedTable().getDomainObjectName()
                + "MapperExt.xml";

        GeneratedXmlFile gxf = new GeneratedXmlFile(document, xmlMapperFileExtName,
                introspectedTable.getMyBatis3XmlMapperPackage(),
                context.getSqlMapGeneratorConfiguration().getTargetProject(), true, context.getXmlFormatter());

        List<GeneratedXmlFile> gxfs = super.contextGenerateAdditionalXmlFiles(introspectedTable);
        if (gxfs == null) {
            gxfs = new ArrayList<GeneratedXmlFile>();
        }
        gxfs.add(gxf);
        return gxfs;
    }

    @Override
    protected void makeSerializable(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

        topLevelClass.addImportedType(serializable);
        topLevelClass.addSuperInterface(serializable);
        String classString = topLevelClass.getType().getFullyQualifiedName();
        Field field = new Field();
        field.setFinal(true);

        field.setInitializationString(getLongHashCode(classString.getBytes()) + "L"); //$NON-NLS-1$
        field.setName("serialVersionUID"); //$NON-NLS-1$
        field.setStatic(true);
        field.setType(new FullyQualifiedJavaType("long")); //$NON-NLS-1$
        field.setVisibility(JavaVisibility.PRIVATE);
        context.getCommentGenerator().addFieldComment(field, introspectedTable);

        topLevelClass.getFields().add(0, field);

    }

    private long getLongHashCode(byte[] value) {
        long h = 0;
        if (value.length > 0) {
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + value[i];
            }
        }
        return h;
    }
}
